package views

import (
	"context"
	"encoding/json"
	. "fastdp-ops/pkg/server/api/middleware"
	"fastdp-ops/pkg/server/api/models"
	. "fastdp-ops/pkg/server/config"
	. "fastdp-ops/pkg/server/grpc"
	pb "fastdp-ops/proto/machine"
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
	"net/http"
	"strings"
	"sync"
	"time"
)

func GetMachineEnv(c *gin.Context) {
	machineList := []models.Machines{}
	if err := DB.Find(&machineList).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": "查询数据库机器列表失败: " + err.Error(),
		})
		return
	}

	// 准备收集所有机器响应的容器
	allResponses := make([]*pb.EnvResponse, 0, len(machineList))
	agentPort := V.GetString("fastdp-agent.port")
	// 并发查询所有机器
	var wg sync.WaitGroup
	var mu sync.Mutex // 保护allResponses的并发安全

	for _, machine := range machineList {

		//addr := machine.MachineIP + ":" + agentPort
		wg.Add(1)
		go func(machine models.Machines) {
			defer wg.Done()
			addr := machine.MachineIP + ":" + agentPort
			var dbMachine models.Machines
			if err := DB.Where("machine_ip = ?", machine.MachineIP).First(&dbMachine).Error; err != nil {
				mu.Lock()
				allResponses = append(allResponses, &pb.EnvResponse{
					MachineId: machine.MachineIP,
					Success:   false,
					Message:   "数据库查询原数据失败: " + err.Error(),
					EnvInfo:   nil,
				})
				mu.Unlock()
				return
			}
			// 1. 获取或创建连接
			conn, err := GetClientConn(addr)
			if err != nil {
				Logger.Error("GetClientConn failed", zap.String("addr", addr), zap.Error(err))
				// 调用失败：更新状态为离线，返回数据库原数据
				updateErr := DB.Model(&models.Machines{}).
					Where("machine_ip = ?", machine.MachineIP).
					Update("status", 0).Error // 0: 离线
				if updateErr != nil {
					Logger.Error("更新离线状态失败", zap.String("ip", machine.MachineIP), zap.Error(updateErr))
				}

				mu.Lock()
				allResponses = append(allResponses, &pb.EnvResponse{
					MachineId: machine.MachineIP,
					Success:   false,
					Message:   "连接Agent失败: " + err.Error(),
					EnvInfo:   convertDBToEnvInfo(machine), // 返回数据库原数据
				})
				mu.Unlock()
				return
			}
			defer conn.Close()
			// 2. 创建客户端
			client := pb.NewMachineEnvServiceClient(conn)

			// 3. 准备上下文（带超时）
			ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
			defer cancel()

			// 4. 发起RPC调用
			resp, err := client.GetMachineEnv(ctx, &pb.EnvRequest{
				MachineId: machine.MachineIP, // 使用当前地址作为机器ID
				InfoTypes: []string{},
			})

			if err != nil {
				// RPC调用失败：标记离线，返回原数据
				updateErr := DB.Model(&models.Machines{}).
					Where("machine_ip = ?", machine.MachineIP).
					Update("status", 0).Error
				if updateErr != nil {
					Logger.Error("更新离线状态失败", zap.String("ip", machine.MachineIP), zap.Error(updateErr))
				}
				mu.Lock()
				allResponses = append(allResponses, &pb.EnvResponse{
					MachineId: machine.MachineIP,
					Success:   false,
					Message:   "RPC调用失败: " + err.Error(),
					EnvInfo:   convertDBToEnvInfo(machine),
				})
				mu.Unlock()
				Logger.Sugar().Error("查询机器 %s 失败: %v", addr, err)
				return
			}
			// 4. RPC成功：对比新信息与数据库原数据是否一致
			newInfo := resp.EnvInfo

			disksJSON, err := json.Marshal(newInfo.Disks)
			if err != nil {
				Logger.Error("磁盘信息序列化失败", zap.Error(err))
			}
			// 5. 信息不一致：更新数据库并返回新信息
			updateData := models.Machines{
				Hostname:      newInfo.Hostname,
				OSName:        newInfo.Os.Name,
				KernelVersion: newInfo.Os.Kernel,
				Arch:          newInfo.Os.Arch,
				Uptime:        newInfo.Uptime,
				CPUName:       newInfo.Cpu.Model,
				CPUCores:      int(newInfo.Cpu.Cores),
				MemoryTotal:   int(newInfo.Memory.TotalMb),
				MemoryFree:    int(newInfo.Memory.FreeMb),
				Disks:         json.RawMessage(disksJSON), // 假设newInfo.Disks是JSON字符串
				Status:        1,                          // 标记为在线
			}
			if err := DB.Model(&models.Machines{}).
				Where("machine_ip = ?", machine.MachineIP).
				Updates(updateData).Error; err != nil {
				Logger.Error("更新机器信息失败", zap.String("ip", machine.MachineIP), zap.Error(err))
				// 更新失败仍返回新信息，但日志记录错误
			}
			mu.Lock()
			allResponses = append(allResponses, resp)
			mu.Unlock()
		}(machine)
	}

	// 等待所有查询完成
	wg.Wait()
	// 创建自定义响应结构体，确保所有字段都能序列化
	type CustomResponse struct {
		MachineId string      `json:"machine_id"`
		Success   bool        `json:"success"`
		Message   string      `json:"message"`
		EnvInfo   interface{} `json:"env_info"`
	}
	// 转换为自定义结构体
	customResponses := make([]CustomResponse, 0, len(allResponses))
	for _, r := range allResponses {
		customResp := CustomResponse{
			MachineId: r.MachineId,
			Success:   r.Success,
			Message:   r.Message,
		}

		// 处理EnvInfo字段
		if r.EnvInfo != nil {
			customResp.EnvInfo = r.EnvInfo
		} else {
			customResp.EnvInfo = "" // 空map而不是nil
		}

		customResponses = append(customResponses, customResp)
	}
	// 直接使用Gin的JSON序列化（避免protojson问题）
	c.JSON(200, gin.H{
		"results": customResponses,
	})

}

// 辅助函数：将数据库原数据转换为EnvInfo格式（用于失败时返回）
func convertDBToEnvInfo(dbMachine models.Machines) *pb.MachineEnvInfo {
	var diskInfos []*pb.DiskInfo
	if len(dbMachine.Disks) > 0 {
		// 将 JSON 数据解析到 diskInfos 切片
		if err := json.Unmarshal(dbMachine.Disks, &diskInfos); err != nil {
			// 处理错误：记录日志或使用默认空切片
			// log.Printf("Error parsing disks JSON: %v", err)
			diskInfos = []*pb.DiskInfo{}
		}
	} else {
		diskInfos = []*pb.DiskInfo{}
	}

	// 根据dbMachine的字段构造EnvInfo，映射关系需与proto定义一致
	return &pb.MachineEnvInfo{
		Os: &pb.SystemInfo{
			Name:   dbMachine.OSName,
			Kernel: dbMachine.KernelVersion,
			Arch:   dbMachine.Arch,
		},
		Cpu: &pb.CpuInfo{
			Model: dbMachine.CPUName,
			Cores: int32(dbMachine.CPUCores),
		},
		Memory: &pb.MemoryInfo{
			TotalMb: int64(dbMachine.MemoryTotal),
			FreeMb:  int64(dbMachine.MemoryFree),
		},
		Disks:    diskInfos,
		Uptime:   dbMachine.Uptime,
		Hostname: dbMachine.Hostname,
	}
}

func Install_agent(c *gin.Context) {
	var builder strings.Builder
	builder.WriteString("curl -fSL http://")
	builder.WriteString(BindIP)
	builder.WriteString(":")
	builder.WriteString(V.GetString("fastdp-ops.port"))
	builder.WriteString("/static/fastdp-agent.sh|bash -x -s ")
	builder.WriteString(BindIP)
	builder.WriteString(" ")
	builder.WriteString(V.GetString("fastdp-ops.port"))
	url := builder.String()

	c.JSON(200, gin.H{
		"agent_install_url": url,
	})
}
